from fastapi import FastAPI, HTTPException, Body
import os
from datetime import datetime, timedelta
from collections import deque
import threading
import time
from pydantic import BaseModel
from typing import Optional

app = FastAPI()

# 缓存结构：{目录路径: {"files": deque, "timestamp": 时间戳}}
cache = {}
cache_lock = threading.Lock()

# 定义请求模型
class RegisterRequest(BaseModel):
    path: str

class PopRequest(BaseModel):
    path: str

class CleanCacheRequest(BaseModel):
    path: Optional[str] = None  # 可选参数，不传则清理所有过期缓存
    force: bool = False         # 是否强制清理（忽略过期时间）

class WriteFileRequest(BaseModel):
    file_path: str
    content: str

def get_all_files(path):
    if os.path.isfile(path):
        return [path]

    """递归获取目录下所有文件，忽略隐藏目录和隐藏文件"""
    file_list = []
    for root, dirs, files in os.walk(path):
        # 过滤隐藏目录（以点开头的目录）
        dirs[:] = [d for d in dirs if not d.startswith('.')]
        
        # 过滤隐藏文件（以点开头的文件）
        non_hidden_files = [f for f in files if not f.startswith('.')]
        
        for file in non_hidden_files:
            file_path = os.path.join(root, file)
            file_list.append(file_path)
    return file_list

def clean_expired_cache(path: str = None, force: bool = False):
    """清理过期缓存"""
    now = datetime.now()
    with cache_lock:
        to_delete = []
        
        # 如果指定了路径，只清理该路径
        if path:
            if path in cache:
                if force or (now - cache[path]["timestamp"]) > timedelta(hours=24):
                    to_delete.append(path)
            return to_delete
        
        # 否则清理所有过期缓存
        for path, entry in cache.items():
            if force or (now - entry["timestamp"]) > timedelta(hours=24):
                to_delete.append(path)
        
        # 执行删除
        for path in to_delete:
            del cache[path]
        
        return len(to_delete)

def background_cleaner(interval: int = 3600):
    """后台清理线程"""
    while True:
        time.sleep(interval)
        count = clean_expired_cache()
        print(f"定时清理完成，清理了 {count} 个过期缓存，当前缓存数量：{len(cache)}")

@app.on_event("startup")
async def startup_event():
    """启动定时清理任务"""
    cleaner = threading.Thread(
        target=background_cleaner,
        daemon=True,
        kwargs={"interval": 60}  # 测试用60秒，生产环境建议3600
    )
    cleaner.start()

@app.post("/register/")
async def register_directory(request: RegisterRequest = Body(...)):
    """注册目录到缓存系统"""
    path = request.path
    if not os.access(path, os.R_OK):
        raise HTTPException(status_code=400, detail="目录不可访问或不存在")
    
    try:
        files = get_all_files(path)
        with cache_lock:
            cache[path] = {
                "files": deque(files),
                "timestamp": datetime.now()
            }
        return {"status": "注册成功", "path": path, "file_count": len(files)}
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"注册失败: {str(e)}")

@app.post("/pop/")
async def pop_file(request: PopRequest = Body(...)):
    """从缓存中获取下一个文件"""
    path = request.path
    with cache_lock:
        if path not in cache:
            raise HTTPException(status_code=404, detail="目录未注册")
        
        entry = cache[path]
        # 双重过期检查
        if datetime.now() - entry["timestamp"] > timedelta(hours=24):
            del cache[path]
            raise HTTPException(status_code=410, detail="缓存已过期")
        
        if not entry["files"]:
            return {"file": None, "remaining": 0}
        
        file = entry["files"].popleft()
        return {"file": file, "remaining": len(entry["files"])}

@app.post("/clean-cache/")
async def manual_clean_cache(request: CleanCacheRequest = Body(...)):
    """手动清理缓存端点"""
    try:
        if request.path:
            # 清理指定路径
            cleaned = clean_expired_cache(path=request.path, force=request.force)
            if cleaned:
                return {"status": "清理成功", "path": request.path}
            return {"status": "无需清理", "path": request.path}
        else:
            # 清理所有过期缓存
            count = clean_expired_cache(force=request.force)
            return {"status": "清理成功", "cleaned_count": count}
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"清理失败: {str(e)}")

@app.get("/cache-status/")
async def get_cache_status():
    """获取当前缓存状态"""
    with cache_lock:
        status = {
            "total_cached_dirs": len(cache),
            "details": []
        }
        
        for path, entry in cache.items():
            status["details"].append({
                "path": path,
                "file_count": len(entry["files"]),
                "last_updated": entry["timestamp"].isoformat(),
                "expires_in": str(timedelta(hours=24) - (datetime.now() - entry["timestamp"]))
            })
        
        return status

@app.post("/write-file/")
async def write_file(request: WriteFileRequest = Body(...)):
    """将内容写入指定文件"""
    file_path = request.file_path
    content = request.content
    
    try:
        # 确保目录存在
        directory = os.path.dirname(file_path)
        if directory and not os.path.exists(directory):
            os.makedirs(directory, exist_ok=True)
        
        # 写入文件
        with open(file_path, 'w', encoding='utf-8') as f:
            f.write(content)
        
        return {"status": "成功", "message": f"文件已写入: {file_path}", "file_size": len(content)}
    
    except PermissionError:
        raise HTTPException(status_code=403, detail="没有写入权限")
    except OSError as e:
        raise HTTPException(status_code=500, detail=f"文件写入失败: {str(e)}")
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"未知错误: {str(e)}")
    


# 新增函数：判断路径类型
def get_path_type(path: str) -> dict:
    """
    检查路径是文件、目录还是不存在
    返回字典包含：
        exists: 是否存在
        type: 'file', 'directory' 或 None
        is_hidden: 是否是隐藏文件/目录
        is_empty: 目录是否为空
    """
    result = {
        "path": path,
        "exists": False,
        "type": None,
        "is_hidden": False,
        "is_empty": False,
        "message": "路径不存在"
    }
    
    if not os.path.exists(path):
        return result
    
    result["exists"] = True
    # 检查是否是隐藏文件/目录（根据文件名或目录名判断）
    basename = os.path.basename(path)
    result["is_hidden"] = basename.startswith('.') if basename else False
    
    if os.path.isfile(path):
        result["type"] = "file"
        result["message"] = "这是一个文件"
    elif os.path.isdir(path):
        result["type"] = "directory"
        result["message"] = "这是一个目录"
        
        # 检查目录是否为空
        try:
            with os.scandir(path) as it:
                # 忽略隐藏文件
                entries = [entry.name for entry in it 
                          if not entry.name.startswith('.') and not entry.is_symlink()]
                result["is_empty"] = len(entries) == 0
        except Exception:
            result["is_empty"] = False
    else:
        result["type"] = "other"
        result["message"] = "特殊文件类型（如设备文件、链接等）"
    
    return result

# 新增请求模型
class PathCheckRequest(BaseModel):
    path: str
    ignore_hidden: bool = True  # 是否在检查目录内容时忽略隐藏文件

# 新增API端点
@app.post("/check-path/")
async def check_path(request: PathCheckRequest = Body(...)):
    """
    检查路径的属性
    - 路径是否存在
    - 是文件还是目录
    - 是否是隐藏文件/目录
    - 目录是否为空（可选是否忽略隐藏文件）
    """
    path = request.path
    result = get_path_type(path)
    
    # 特殊处理：当路径存在且是目录时，根据参数检查是否忽略隐藏文件
    if result["exists"] and result["type"] == "directory" and not request.ignore_hidden:
        try:
            with os.scandir(path) as it:
                # 包含所有文件（不忽略隐藏文件）
                entries = [entry.name for entry in it 
                          if not entry.is_symlink()]
                result["is_empty"] = len(entries) == 0
        except Exception:
            pass
    
    return result

# 新增请求模型
class ReadFileRequest(BaseModel):
    file_path: str
    encoding: str = "utf-8"  # 默认使用UTF-8编码
    as_base64: bool = False  # 是否返回Base64编码内容（适用于二进制文件）

# 新增API端点
@app.post("/read-file/")
async def read_file(request: ReadFileRequest = Body(...)):
    """
    读取文件内容，支持文本文件和二进制文件
    - 支持指定文件编码
    - 支持返回Base64编码格式（适用于二进制文件）
    - 自动处理大文件分块读取
    """
    file_path = request.file_path
    
    # 检查文件是否存在
    if not os.path.exists(file_path):
        raise HTTPException(status_code=404, detail="文件不存在")
    
    if not os.path.isfile(file_path):
        raise HTTPException(status_code=400, detail="路径不是文件")
    
    try:
        # 获取文件大小
        file_size = os.path.getsize(file_path)
        
        # 大文件处理（>1MB）
        if file_size > 1024 * 1024:  # 1MB
            return StreamingResponse(
                content=read_file_in_chunks(file_path, request.encoding, request.as_base64),
                media_type="application/octet-stream" if request.as_base64 else "text/plain",
                headers={
                    "Content-Disposition": f"attachment; filename={os.path.basename(file_path)}",
                    "X-File-Size": str(file_size)
                }
            )
        
        # 小文件处理
        file_mode = "rb" if request.as_base64 else "r"
        with open(file_path, file_mode, encoding=request.encoding if not request.as_base64 else None) as f:
            content = f.read()
            
            if request.as_base64:
                import base64
                content = base64.b64encode(content).decode("utf-8")
                return {
                    "status": "success",
                    "file_path": file_path,
                    "content": content,
                    "encoding": "base64",
                    "size": file_size
                }
            else:
                return {
                    "status": "success",
                    "file_path": file_path,
                    "content": content,
                    "encoding": request.encoding,
                    "size": file_size
                }
    
    except UnicodeDecodeError:
        raise HTTPException(
            status_code=400,
            detail=f"解码错误，请尝试使用其他编码（当前编码: {request.encoding}）"
        )
    except PermissionError:
        raise HTTPException(status_code=403, detail="没有读取权限")
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"文件读取失败: {str(e)}")

def read_file_in_chunks(file_path: str, encoding: str, as_base64: bool):
    """
    分块读取大文件内容（生成器函数）
    """
    chunk_size = 1024 * 64  # 64KB分块
    
    try:
        with open(file_path, "rb" if as_base64 else "r", 
                 encoding=encoding if not as_base64 else None) as f:
            
            while True:
                chunk = f.read(chunk_size)
                if not chunk:
                    break
                
                if as_base64:
                    import base64
                    yield base64.b64encode(chunk).decode("utf-8")
                else:
                    yield chunk
    
    except Exception as e:
        # 处理流式读取中的异常
        raise HTTPException(status_code=500, detail=f"文件流读取失败: {str(e)}")